package scenic.study.net.socket;


import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import scenic.MyLogger;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 为了消息可以点对点的交流，需要客户端连接成功socket之后，有个身份认证的机制
 * 在socket连接成功之后，客户端给服务器发送一个指令，如下格式
 *
 * @identity:clientid:socketType clientid 是String类型的数据
 * socketType 是Integer类型的，取值  1代表来自模拟器， 2代表来自控制程序，3代表来自命令行
 * <printToFile>
 * 同时控制程序再发送每一条命令的时候都需要通过JSON携带clientid的数据
 * <printToFile>
 * Created by scenic on 15/11/5.
 */
public class NBSocketServer {

    private static ReadWriteLock myLock = new ReentrantReadWriteLock(false);


    private static String getTTime() {
        Calendar c = Calendar.getInstance();
        c.clear();
        c.setTimeInMillis(System.currentTimeMillis());
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH) + 1;
        int date = c.get(Calendar.DATE);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);
        int second = c.get(Calendar.SECOND);
        String time =
                year + "-" +
                        month + "-" +
                        date + "_" +
                        hour + "-" +
                        minute + "-" +
                        second;
        return time;
    }

    private File logFile = new File(System.getProperty("user.dir") + "/" + getTTime() + "_log.txt");

    private String version = "1.3";
    private static BufferedWriter logOutput;
    private Logger logger = MyLogger.get(NBSocketServer.class);


    private List<SocketWrapper> socketWrappers = Collections.synchronizedList(new ArrayList<SocketWrapper>());
    private List<OnReceiverMessage> listener = new ArrayList<>();

    public NBSocketServer(OnReceiverMessage onReceiverMessage) {
        listener.add(onReceiverMessage);
    }

    public void listen() {
        try {
            logOutput = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(logFile))));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

        try {
            logger.info("begin socket log file " + logFile.getAbsolutePath() + " version " + version);
            ServerSocket socket = new ServerSocket(8000);
            logger.info("listening port　: " + socket.getLocalPort() + "　address　: " + InetAddress.getLocalHost().getHostAddress());
            while (true) {
                Socket sc = socket.accept();
                socketWrappers.add(new SocketWrapper(sc));
                logger.debug("Create SocketWrapper Ip: " + sc.getLocalAddress().getHostAddress() + "  socket size : " + socketWrappers.size());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class SocketWrapper {
        private String name = "unknown";
        private InputStream inputStream = null;
        private OutputStream outputStream = null;
        private Socket socket = null;

        private Thread readerThread;
        private boolean isRun = true;

        private Calendar cc = Calendar.getInstance();//可以对每个时间域单独修改

        // 只有一条线程可以获取到这个方法
        Calendar c = Calendar.getInstance();//可以对每个时间域单独修改

        private synchronized String getTime() {
            cc.clear();
            cc.setTimeInMillis(System.currentTimeMillis());
            int year = cc.get(Calendar.YEAR);
            int month = cc.get(Calendar.MONTH) + 1;
            int date = cc.get(Calendar.DATE);
            int hour = cc.get(Calendar.HOUR_OF_DAY);
            int minute = cc.get(Calendar.MINUTE);
            int second = cc.get(Calendar.SECOND);
            int milisecond = cc.get(Calendar.MILLISECOND);
            String time = year + "-" + month + "-" + date + "_"
                    + hour + "-" + minute + "-" + second + ":" + milisecond;
            return time;
        }

        private void close() {
            isRun = false;
            try {
                printToFile("close：" + name);
                outputStream.close();
                inputStream.close();
                socket.close();
                readerThread.interrupt();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                socketWrappers.remove(SocketWrapper.this);
            }
        }

        public SocketWrapper(Socket socket) {
            this.socket = socket;
            try {
                inputStream = socket.getInputStream();
                outputStream = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }

            readerThread = new Thread(new ReaderThread());
            readerThread.start();

//			try {
//				if (socket.isConnected()) {
//					byte[] b = new byte[1024*100];
//					int value = inputStream.read(b);
//
//					if(value > 0){
//						String str = new String(b,0,value);
//						if(str.startsWith("@identity")){
//							//身份验证消息
//							try {
//								String values[] = str.split(":");
//								name = values[1];
//								socketType = Integer.parseInt(values[2]);
//								printToFile("Get Identity Msg " + str + "  " + socket.getLocalAddress().getHostAddress());
//							} catch (Exception e) {
//								sendMessage("The msg fomat error " + e.getMessage());
//								System.err.println("The msg fomat error " + e.getMessage());
//							}
//						}
//					}
//
//					// 这里需要有一把锁来控制
//					myLock.writeLock().lock();
//					for(int i = 0; i < socketWrappers.size(); i++){
//						SocketWrapper tmp = socketWrappers.get(i);
//						if(tmp.name != null && tmp.name.equals(name)){
//							printToFile("remove the same client id " + name);
//
//							//socketWrappers.remove(tmp);
//							tmp.close();
//
//
//
//							i--;
//						}
//					}
//					myLock.writeLock().unlock();
//
//				}
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
        }

        public boolean sendMessage(String msg) {
            try {
                outputStream.write(msg.getBytes());
                outputStream.flush();
                return true;
            } catch (SocketException e) {
                String log = "SocketException " + e.getMessage() + "  " + this + " will remove  this SocketWrapper ";
                logger.debug(log);
                printToFile(log);

                close();
            } catch (IOException e) {
                printToFile("IOException " + e.getMessage() + " will remove  this SocketWrapper ");
                e.printStackTrace();
                close();
            }
            return false;
        }

//        public void sendMessageToOther(String msg) {
//            switch (socketType) {
//                case MSG_TYPE_SIMULATE:
//                    //模拟器发送给其他的客户端
//                    JSONObject jsonObj = null;
//                    try {
//                        jsonObj = new JSONObject(msg);
//                        ;
//
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//
//                    String msgClientID = null;
//                    if (jsonObj == null) {
//                        e("Error The JSON Str InValide " + msg);
//                    } else {
//                        if (jsonObj.has("name")) {
//                            msgClientID = jsonObj.getString("name");
//                        } else {
//                            e("The msg can not find the name  " + msg);
//                        }
//                    }
//
//                    //这里需要有一把锁来控制
//                    myLock.readLock().lock();
//                    for (int i = 0; i < socketWrappers.size(); i++) {
//                        SocketWrapper tmp = socketWrappers.get(i);
//                        if (tmp == this) {
//                            continue;
//                        } else if (tmp.socketType == MSG_TYPE_DEBUG_CMD) {
//                            tmp.sendMessage(msg);
//                        } else if (tmp.socketType == MSG_TYPE_CONTORL) {
//                            //如果是控制程序的话，需要进一步筛选
//                            if (msgClientID != null) {
//                                if (tmp.name.equals(msgClientID)) {
//                                    if (true == tmp.sendMessage(msg))
//                                        p(name + "----R---->" + tmp.name + " --->Success \r MSG " + msg);
//                                    else
//                                        e(name + "----R---->" + tmp.name + " --->Failure \r MSG " + msg);
//
//                                    System.out.println();
//                                }
//                            } else {
//                                tmp.sendMessage(msg);
//                            }
//                        } else {
//                            e("the socketType error " + tmp.socketType);
//                        }
//                    }
//                    myLock.readLock().unlock();
//
//                    break;
//
//                case MSG_TYPE_DEBUG_CMD:
//                    //这里需要有一把锁来控制
//                    myLock.readLock().lock();
//                    for (int i = 0; i < socketWrappers.size(); i++) {
//                        SocketWrapper tmp = socketWrappers.get(i);
//                        if (tmp == this) {
//                            continue;
//                        }
//                        if (tmp.socketType == MSG_TYPE_SIMULATE ||
//                                tmp.socketType == MSG_TYPE_CONTORL) {
//                            tmp.sendMessage(msg);
//                        }
//                    }
//                    myLock.readLock().unlock();
//                    break;
//
//                case MSG_TYPE_CONTORL:
//                    //来自控制端的消息，需要发送给模拟器和命令行
//                    //这里需要有一把锁来控制
//                    myLock.readLock().lock();
//                    for (int i = 0; i < socketWrappers.size(); i++) {
//                        SocketWrapper tmp = socketWrappers.get(i);
//                        if (tmp == this) {
//                            continue;
//                        }
//
//                        if (tmp.socketType == MSG_TYPE_DEBUG_CMD) {
//                            tmp.sendMessage(msg);
//                        }
//
//                        if (tmp.socketType == MSG_TYPE_SIMULATE) {
//
//                            if (true == tmp.sendMessage(msg))
//                                p(name + "----S---->" + tmp.name + " --> Success   \r MSG " + msg);
//                            else
//                                p(name + "----S---->" + tmp.name + " --> Failure   \r MSG " + msg);
//                        }
//                    }
//                    myLock.readLock().unlock();
//                    break;
//
//                default:
//                    myLock.readLock().lock();
//                    for (int i = 0; i < socketWrappers.size(); i++) {
//                        SocketWrapper tmp = socketWrappers.get(i);
//                        if (tmp != this) {
//                            tmp.sendMessage(msg);
//                        }
//                    }
//                    myLock.readLock().unlock();
//                    break;
//            }
//        }

        private void printToFile(String str) {
            if (str != null) {
                writeFileAndPrint("=============================" + getTime() + "==================================");
                writeFileAndPrint(str);
                writeFileAndPrint("===================================================================\n");
            } else {
                System.out.println();
            }
        }


        private synchronized void writeFileAndPrint(String str) {
            try {
                logOutput.write(str);
                logOutput.newLine();
                logOutput.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "[SocketWrapper id = " + name + "]";
        }

        private class ReaderThread implements Runnable {
            @Override
            public void run() {
                while (isRun) {
                    try {
                        Thread.sleep(1);

                        if (socket.isConnected()) {
                            byte[] b = new byte[1024 * 1024];

                            int length;
                            List<byte[]> data = new ArrayList<>();
                            while ((length = inputStream.read(b)) > 0) {
                                data.add(Arrays.copyOf(b, length));
                            }
                            listener.forEach(l -> l.onReceiverMessage(data));
                        } else {
                            printToFile(" socket is not connected");
                        }

                    } catch (SocketException e) {
                        String str = "ReaderThread SocketException Error " + e.getMessage() + "  " + SocketWrapper.this + " will remove this ";
                        logger.debug(str);
                        close();
                    } catch (InterruptedException e) {
                        String str = "ReaderThread InterruptedException Error " + e.getMessage() + "  " + SocketWrapper.this + " will remove this ";
                        logger.debug(str);
                        close();
                    } catch (Exception e) {
                        logger.debug("ReaderThread  Error " + e.getMessage() + "  " + SocketWrapper.this + " will remove this ");
                        close();
                    }
                }
            }
        }
    }

    private void closeDebugLog() {
        logger.setLevel(Level.INFO);
    }

    public interface OnReceiverMessage {
        void onReceiverMessage(List<byte[]> message);
    }

    public static class Heartbeat extends Thread {
        public boolean isRun = true;
        private List<SocketWrapper> socketWrappers;

        public Heartbeat(List<SocketWrapper> wrapper) {
            this.socketWrappers = wrapper;
        }

        @Override
        public void run() {
            while (isRun) {
                for (int i = 0; i < socketWrappers.size(); i++) {
                    socketWrappers.get(i).sendMessage("heart");
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }



    public static void main(String args[]) {
        Logger logger = MyLogger.get(NBSocketServer.class);
        NBSocketServer server = new NBSocketServer(
                message -> message.forEach(m -> {
                    String msg = new String(m);
                    if (msg.startsWith("GET /?message")) {
                        String tmpM = msg.split("\r\n")[0];
                        logger.info(URLDecoder.decode(tmpM));
                    }

                }));

        new Heartbeat(server.socketWrappers).start();
        server.closeDebugLog();
        server.listen();
    }


}




